using System;
using System.Threading;

using Reign.Core;
using Reign.Core.MathF32;
using Reign.Video;
//using Reign.Audio;

#if WINDOWS && !XNA
using Reign.Input.WinForms;
using Reign.Audio.XAudio;
#endif

#if XNA
using Reign.Input.XNA;
using Reign.Video.XNA;
#endif

#if OSX
using Reign.Input.Cocoa;
using Reign.Audio.Cocoa;
using MonoMac.Foundation;
#endif

#if LINUX
using Reign.Input.X11;
using Reign.Audio.OpenAL;
#endif

#if iOS
using MonoTouch.Foundation;
using Reign.Input.iOS;
using Reign.Audio.Cocoa;
#endif

#if ANDROID
using Reign.Input.Android;
using Reign.Audio.Android;
#endif

#if NaCl
//using Reign.Audio.Dumby;
using Reign.Input.NaCl;
#endif

#if D3D11
using Reign.Video.D3D11;
#endif

#if D3D10
using Reign.Video.D3D10;
#endif

#if D3D9
using Reign.Video.D3D9;
#endif

#if GL
using Reign.Video.OpenGL;
#endif

namespace WaterDemo
{
	public class Game
	{
		// Looping
		#if XNA
		Microsoft.Xna.Framework.Game xnaGame;
		Microsoft.Xna.Framework.Graphics.GraphicsDevice device;
		#elif iOS || ANDROID
		Application application;
		#else
		private Window window;
		#endif
		private Reign.Core.MathI32.Vector2 size;
		int loopTime;
		int fps = 0, fpsTime = Environment.TickCount;
		public int FPS;

		// Simulate
		const int simulateSize = 128;
		private ViewPort simulateViewPort;
		private Shader simulateShader;
		private BufferLayout simulateBufferLayout;
		private RenderTarget[] simulateRenderTarget;
		private ShaderResource simulateTextureResource;
		private ShaderVariable simulateSizeVariable, simulateSampleOffsets;

		// Normal
		private Shader normalShader;
		private BufferLayout normalBufferLayout;
		private RenderTarget normalRenderTarget;
		private ShaderResource normalTextureResource;
		private ShaderVariable normalSizeVariable, normalSampleOffsets;

		// Present
		private ViewPort presentViewPort;
		private Shader presentShader;
		private BufferLayout presentBufferLayout;
		private ShaderResource presentNormalTextureResource, presentBackgroundTextureResource;
		private ShaderVariable presentSizeVariable;

		// Drop
		private Shader dropShader;
		private ShaderVariable dropLocation;
		private BufferLayout dropBufferLayout;

		// Background
		private Texture2D[] backgroundTexture;
		private int backgroundTexureIndex;

		// States
		private RasterizerState rasterizerState;
		private DepthStencilState depthStencilState;
		private SamplerState samplerState;

		// Misc
		private RootDisposable rootDisposable;
		private Video video;
		private QuickDraw q;
		private Input input;
		#if (WINDOWS && !XNA) || OSX || LINUX || NaCl
		private Mouse mouse;
		private Keyboard keyboard;
		#endif
		#if XNA
		private GamePad gamePad;
		Vector2 mouseLoc = new Vector2(256, 256);
		#endif
		#if iOS || ANDROID
		private TouchScreen touchScreen;
		#endif
		
		// Sound
		//Audio audio;
		//SoundWAV sound;
		//SoundWAVInstance soundInstance;

		#if XNA
		public Game(WaterDemo_XNA.XNAGame xnaGame, Microsoft.Xna.Framework.GraphicsDeviceManager graphics)
		#elif iOS || ANDROID
		public Game(Application application)
		#else
		public Game(Window window, Video video)
		#endif
		{
			#if XNA
			this.xnaGame = xnaGame;
			this.device = graphics.GraphicsDevice;
			size = new Reign.Core.MathI32.Vector2(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
			#elif iOS || ANDROID
			this.application = application;
			this.size = application.FrameSize;
			#else
			this.window = window;
			this.size = window.FrameSize;
			this.video = video;
			#endif

			try
			{
				init();
				loopTime = Environment.TickCount;
			}
			catch (Exception e)
			{
				Dispose();
				throw e;
			}
		}

		public void Dispose()
		{
			if (rootDisposable != null)
			{
				rootDisposable.Dispose();
				rootDisposable = null;
			}
		}
		
		private void init()
		{
			#if iOS || ANDROID || NaCl
			var surfaceFormat = SurfaceFormats.RGBAx8;
			#else
			var surfaceFormat = SurfaceFormats.RGBx10_Ax2;
			#endif

			#if WINDOWS || LINUX
			string shaderDirectory = "../../../../Data/Shaders/";
			string textureDir = "../../../../Data/Images/";
			string audioDir = "../../../../Data/Audio/";
			#endif
			
			#if  OSX || iOS
			string shaderDirectory = "Data/Shaders/";
			string textureDir = "Data/Images/";
			string audioDir = "Data/Audio/";
			#endif
			
			#if iOS
			shaderDirectory = "Data/Shaders/GLES2_";
			#endif
			#if ANDROID || NaCl
			string shaderDirectory = "Data/Shaders/GLES2_";
			string textureDir = "Data/Images/";
			string audioDir = "Data/Audio/";
			#endif

			// Misc
			rootDisposable = new RootDisposable();
			//audio = new Audio(rootDisposable);
			//sound = new SoundWAV(audio, audioDir+"loop.wav", 1, true);
			//sound.Play();

			#if XNA
			input = new Input(rootDisposable);
			gamePad = new GamePad(input);
			video = new Video(rootDisposable, device, xnaGame.Content);
			#elif iOS
			video = new Video(rootDisposable, application, true);
			input = new Input(rootDisposable, application);
			touchScreen = new TouchScreen(input);
			#elif ANDROID
			video = new Video(rootDisposable, true);
			input = new Input(rootDisposable, application);
			touchScreen = new TouchScreen(input);
			#else
			input = new Input(rootDisposable, window);
			mouse = new Mouse(input);
			keyboard = new Keyboard(input);
			if (video == null) video = new Video(rootDisposable, window, true);
			#endif
			
			#if D3D11
			shaderDirectory += "D3D10_";
			var shaderversion = ShaderVersions.Max;
			#endif
			#if D3D10
			shaderDirectory += "D3D10_";
			var shaderversion = ShaderVersions.HLSL_4_0;
			#endif
			#if D3D9
			shaderDirectory += "D3D9_";
			var shaderversion = video.Caps.MaxShaderVersion;
			Message.Show("Info", shaderversion.ToString());
			Message.Show("Info", string.Format("Is D3D9Ex: {0}", video.Caps.D3D9Ex));
			#endif
			#if XNA
			shaderDirectory = "Shaders/XNA_";
			textureDir = "Images/";
			var shaderversion = ShaderVersions.Unknown;
			#endif
			#if GL
			#if iOS || ANDROID || NaCl
			var shaderversion = ShaderVersions.GLSL_1_00;
			#else
			var shaderversion = video.Caps.MaxShaderVersion;
			if (video.Caps.Version == Versions.GL2) shaderDirectory += "GL2_";
			else shaderDirectory += "GL3_";
			#endif
			#endif

			var bufferLayoutDesc = new BufferLayoutDesc(BufferLayoutTypes.Position3_UV);
			q = new QuickDraw(video, bufferLayoutDesc);

			// States
			depthStencilState = new DepthStencilState(video, new DepthStencilStateDesc(DepthStencilStateTypes.None));
			rasterizerState = new RasterizerState(video, new RasterizerStateDesc(RasterizerStateTypes.Solid_CullNone));
			samplerState = new SamplerState(video, new SamplerStateDesc(SamplerStateTypes.Linear_Clamp));

			// Simulate
			simulateViewPort = new ViewPort(video, 0, 0, simulateSize, simulateSize);
			simulateShader = new Shader(video, shaderDirectory+"Simulate.rs", shaderversion);
			simulateBufferLayout = new BufferLayout(video, simulateShader, bufferLayoutDesc);

			simulateRenderTarget = new RenderTarget[2];
			simulateRenderTarget[0] = new RenderTarget(video, simulateSize, simulateSize, MultiSampleTypes.None, surfaceFormat, RenderTargetUsage.DiscardContents);
			simulateRenderTarget[1] = new RenderTarget(video, simulateSize, simulateSize, MultiSampleTypes.None, surfaceFormat, RenderTargetUsage.DiscardContents);
			simulateTextureResource = (ShaderResource)simulateShader.Resource("MyTexture");
			simulateSizeVariable = (ShaderVariable)simulateShader.Variable("simulateSize");
			simulateSampleOffsets = (ShaderVariable)simulateShader.Variable("sampleOffsets");
			
			// Normal
			normalShader = new Shader(video, shaderDirectory+"Normal.rs", shaderversion);
			normalBufferLayout = new BufferLayout(video, normalShader, bufferLayoutDesc);

			normalRenderTarget = new RenderTarget(video, simulateSize, simulateSize, MultiSampleTypes.None, surfaceFormat, RenderTargetUsage.DiscardContents);
			normalTextureResource = (ShaderResource)normalShader.Resource("MyTexture");
			normalSizeVariable = (ShaderVariable)normalShader.Variable("simulateSize");
			normalSampleOffsets = (ShaderVariable)normalShader.Variable("sampleOffsets");
			
			// Drop
			dropShader = new Shader(video, shaderDirectory+"Drop.rs", shaderversion);
			dropBufferLayout = new BufferLayout(video, dropShader, bufferLayoutDesc);
			dropLocation = (ShaderVariable)dropShader.Variable("Location");

			// Present
			presentViewPort = new ViewPort(video, 0, 0, (int)size.X, (int)size.Y);
			presentShader = new Shader(video, shaderDirectory+"Present.rs", shaderversion);
			presentBufferLayout = new BufferLayout(video, presentShader, bufferLayoutDesc);
			presentNormalTextureResource = (ShaderResource)presentShader.Resource("NormalTexture");
			presentBackgroundTextureResource = (ShaderResource)presentShader.Resource("BackgroundTexture");
			#if D3D9 || XNA
			presentSizeVariable = (ShaderVariable)presentShader.Variable("simulateSize");
			#endif

			// Background
			#if iOS || ANDROID || NaCl
			backgroundTexture = new Texture2D[1];
			backgroundTexture[0] = new Texture2D(video, textureDir+"face.bmpc");
			//if (video.Caps.TextureCompression_ATC) backgroundTexture[0] = new Texture2D(video, textureDir+"ExplosionATC.DDS");
			//else if (video.Caps.TextureCompression_S3TC) backgroundTexture[0] = new Texture2D(video, textureDir+"face.dds");
			//else if (video.Caps.TextureCompression_PVR) backgroundTexture[0] = new Texture2D(video, textureDir+"Explosion.pvr");
			//else backgroundTexture[0] = new Texture2D(video, textureDir+"color.png");
			#else
			backgroundTexture = new Texture2D[3];
			backgroundTexture[0] = new Texture2D(video, textureDir+"bw.png");
			backgroundTexture[1] = new Texture2D(video, textureDir+"color.png");
			backgroundTexture[2] = new Texture2D(video, textureDir+"girl.jpg");
			#endif
		}
		
		public void Update()
		{
			input.Update();

			#if XNA
			if (gamePad.X.Down) backgroundTexureIndex = 0;
			if (gamePad.Y.Down) backgroundTexureIndex = 1;
			if (gamePad.B.Down) backgroundTexureIndex = 2;
			
			mouseLoc += new Vector2(gamePad.LeftStick.X, -gamePad.LeftStick.Y) * 5f;
			if (mouseLoc.X < 0) mouseLoc.X = 0;
			if (mouseLoc.X >= size.X) mouseLoc.X = size.X-1;
			if (mouseLoc.Y < 0) mouseLoc.Y = 0;
			if (mouseLoc.Y >= size.Y) mouseLoc.Y = size.Y-1;

			if (gamePad.Back.Up) xnaGame.Exit();
			#elif iOS || ANDROID
			// Do nothing
			#else
			if (keyboard.D1.Down) backgroundTexureIndex = 0;
			if (keyboard.D2.Down) backgroundTexureIndex = 1;
			if (keyboard.D3.Down) backgroundTexureIndex = 2;

			/*if (keyboard.Esc.Up)
			{
				window.Close();
			}*/
			#endif
		}

		bool swap;
		int dropTic;
		public void Render()
		{
			video.Update();
			int swapIndex = Convert.ToInt32(swap);
			int swapIndex2 = Convert.ToInt32(!swap);

			depthStencilState.Enable();
			rasterizerState.Enable();
			samplerState.Enable(0);
			samplerState.Enable(1);

			Vector2[] sampleOffsets = new Vector2[8]
			{
				new Vector2(-0.707106769f, -0.707106769f),
				new Vector2(-1, 0),
				new Vector2(-0.707106769f, 0.707106769f),
	
				new Vector2(0, -1),
				new Vector2(0, 1),
	
				new Vector2(0.707106769f, -0.707106769f),
				new Vector2(1, 0),
				new Vector2(0.707106769f, 0.707106769f)
			};

			// Simulate
			simulateRenderTarget[swapIndex].Enable();
			simulateViewPort.Apply(simulateRenderTarget[swapIndex]);
			simulateTextureResource.Set(simulateRenderTarget[swapIndex2]);
			simulateBufferLayout.Enable();
			simulateSizeVariable.Set(1f/(float)simulateSize);
			simulateSampleOffsets.Set(sampleOffsets);
			simulateShader.Apply();
			DrawQuad(1);

			// Draw Drop
			#if XNA
			if (gamePad.A.On) drawDrop(mouseLoc.X, mouseLoc.Y);
			#elif iOS || ANDROID
			foreach (var touch in touchScreen.Touches)
			{
				if (touch.On) drawDrop(touch.Location.X, touch.Location.Y);
			}
			#else
			if (mouse.Left.On) drawDrop(mouse.Location.X, mouse.Location.Y);
			//drawDrop(mouse.Location.X, mouse.Location.Y);
			/*if (dropTic == 0) drawDrop(256, 256);
			++dropTic;
			if (dropTic == 160) dropTic = 0;*/
			#endif
			
			// Calculate Normal map
			normalRenderTarget.Enable();
			normalTextureResource.Set(simulateRenderTarget[swapIndex]);
			normalBufferLayout.Enable();
			normalSizeVariable.Set(1f/(float)simulateSize);
			normalSampleOffsets.Set(sampleOffsets);
			normalShader.Apply();
			DrawQuad(1);
			
			// Present
			video.EnableRenderTarget(null);
			presentViewPort.Apply(null);
			presentNormalTextureResource.Set(normalRenderTarget);
			presentBackgroundTextureResource.Set(backgroundTexture[backgroundTexureIndex]);
			#if D3D9 || XNA
			presentSizeVariable.Set(1f/(float)simulateSize);
			#endif
			presentBufferLayout.Enable();
			presentShader.Apply();
			DrawQuad(1);
			
			#if !XNA && !iOS && !ANDROID
			video.Present();
			#endif
			swap = !swap;

			#if DEBUG
			int current = Environment.TickCount;
		    ++fps;
		    if (System.Math.Abs(current - fpsTime) >= 1000)
		    {
				#if !XNA
				Console.WriteLine(fps.ToString());
				#endif
				FPS = fps;
		        fps = 0;
		        fpsTime = current;
		    }
			#endif
		}

		private void drawDrop(float x, float y)
		{
			Vector2 loc = new Vector2(x, y) - (new Vector2(size.X, size.Y) * .5f);
			loc.X /= size.X / 2;
			loc.Y /= size.Y / 2;

			dropBufferLayout.Enable();
			dropLocation.Set(loc.X, loc.Y);
			dropShader.Apply();
			DrawQuad(.1f);
		}

		private void DrawQuad(float size)
		{
			q.StartTriangles();
				q.Color(1, 1, 1, 1);
				q.UV(0, 0); q.Pos(-size, -size, 0);
				q.UV(0, 1); q.Pos(-size, size, 0);
				q.UV(1, 1); q.Pos(size, size, 0);

				q.UV(0, 0); q.Pos(-size, -size, 0);
				q.UV(1, 1); q.Pos(size, size, 0);
				q.UV(1, 0); q.Pos(size, -size, 0);
			q.End();
		}
	}
}

